package cz.muni.fi.pa165.hauntedhouses.facade;

import cz.muni.fi.pa165.hauntedhouses.dto.AbilityDTO;
import cz.muni.fi.pa165.hauntedhouses.dto.AbilityType;
import cz.muni.fi.pa165.hauntedhouses.dto.AccountDTO;
import cz.muni.fi.pa165.hauntedhouses.dto.GhostDTO;
import cz.muni.fi.pa165.hauntedhouses.dto.HouseDTO;
import cz.muni.fi.pa165.hauntedhouses.dto.HumanDTO;
import cz.muni.fi.pa165.hauntedhouses.entity.Level;
import cz.muni.fi.pa165.hauntedhouses.service.AbilityService;
import cz.muni.fi.pa165.hauntedhouses.service.GhostService;
import cz.muni.fi.pa165.hauntedhouses.service.HouseService;
import cz.muni.fi.pa165.hauntedhouses.service.HumanService;
import cz.muni.fi.pa165.hauntedhouses.service.SecurityService;
import cz.muni.fi.pa165.hauntedhouses.util.GhostMasterException;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * A facade class used as layer between service and presentation.
 * Also includes special methods of our application which require all the services together.
 *
 * @author Jan Vyhnánek & Jaromir Benda & Documented by Siliarus
 */
@Service
public class ServiceFacadeImpl implements ServiceFacade {

    @Autowired
    private GhostService ghostService;
    
    @Autowired
    private HumanService humanService;
    
    @Autowired
    private HouseService houseService;
    
    @Autowired
    private AbilityService abilityService;
    
    @Autowired
    private SecurityService securityService;

    @Override
    public GhostService getGhostService() {
        return ghostService;
    }

    @Override
    public void setGhostService(GhostService ghostService) {
        this.ghostService = ghostService;
    }

    @Override
    public void setHumanService(HumanService humanService) {
        this.humanService = humanService;
    }

    @Override
    public void setHouseService(HouseService houseService) {
        this.houseService = houseService;
    }

    @Override
    public void setAbilityService(AbilityService abilityService) {
        this.abilityService = abilityService;
    }
    
    @Override
    public void setSecurityService(SecurityService securityService) {
        this.securityService = securityService;
    }
    
    @Override
    public void createHouse(HouseDTO house) {
        houseService.create(house);
        for(GhostDTO ghost: house.getGhosts()){
            if(ghost.getId() == null){
                createGhost(ghost);
            }
        }
        for(HumanDTO human: house.getHumans()){
            if(human.getId() == null){
                createHuman(human);
            }
        }
    }

    @Override
    public void updateHouse(HouseDTO house) {
        houseService.update(house);
    }

    @Override
    public void removeHouse(HouseDTO house) {
        houseService.remove(house);
    }

    @Override
    public HouseDTO getHouse(Long id) {
        return houseService.get(id);
    }

    @Override
    public List<HouseDTO> findAllHouses() {
        return houseService.findAll();
    }

    @Override
    public List<HouseDTO> findHousesByName(String name) {
        return houseService.findByName(name);
    }

    /**
     * Inserts a single Human into a House. This method works with DTO objects already.
     *
     * @param house A house which we want to add human to.
     * @param human A human which is to be added into a House
     */
    @Transactional
    @Override
    public void addHumanToHouse(HouseDTO house, HumanDTO human) {
        if (house == null || human == null) {
            throw new IllegalArgumentException("House or Human are null");
        }
        if (house.getId() == null) {
            throw new IllegalArgumentException("House isn't already created");
        }
        if (human.getId() == null) {
            throw new IllegalArgumentException("Human isn't already created");
        }

        List<HumanDTO> humans = house.getHumans();
        if (humans.size() <= house.getHumanCapacity()) {
            if (!(humans.contains(human))) {
                humans.add(human);
                house.setHumans(humans);
                houseService.update(house);
            } else {
                throw new IllegalArgumentException("This house already has the inhibitant you are trying to insert");
            }
        } else {
            throw new GhostMasterException("Too many humans already,cannot insert next human");
        }
    }

    /**
     * Removes a single person from a house. Works with DTO.
     *
     * @param house A target house where we want to removeGhost the human
     * @param human A human to be EXTERMINATED
     */
    @Transactional
    @Override
    public void removeHumanFromHouse(HouseDTO house, HumanDTO human) {
        if (house == null || human ==null) {
            throw new IllegalArgumentException("house or human are null");
        }
        if (house.getId() == null) {
            throw new IllegalArgumentException("House isn't already created");
        }
        if (human.getId() == null) {
            throw new IllegalArgumentException("Human isn't already created");
        }

        humanService.remove(human);

    }

    @Override
    public void createGhost(GhostDTO ghost) {
        for (AbilityDTO ability : ghost.getAbilities()) {
            if (ability.getId() == null) {
                createAbility(ability);
            }
        }
        ghostService.create(ghost);
        
    }

    @Override
    public void updateGhost(GhostDTO ghost) {
        ghostService.update(ghost);
    }

    @Override
    public void removeGhost(GhostDTO ghost) {
        ghostService.remove(ghost);
    }

    @Override
    public GhostDTO getGhost(Long id) {
        return ghostService.get(id);
    }

    @Override
    public List<GhostDTO> findAllGhosts() {
        return ghostService.findAll();
    }

    @Override
    public List<GhostDTO> findGhostsByName(String name) {
        return ghostService.findByName(name);
    }

    @Override
    public List<GhostDTO> findGhostsByHouse(HouseDTO house) {
        return ghostService.findByHouse(house);
    }

    @Override
    public List<GhostDTO> findGhostsByLevel(Integer level) {
        return ghostService.findByLevel(level);
    }

    @Override
    public List<GhostDTO> findGhostsByType(AbilityType type) {
        return ghostService.findByType(type);
    }

    /**
     * Given the used ability, ghost owning this ability and targeted human this method scared the human, lowering his psyche and possibly scaring him
     * to death
     *
     * @param ability Used ability.
     * @param human Human to be scared.
     * @param ghost Ghost which uses the ability.
     */
    @Transactional
    @Override
    public void useAbility(AbilityDTO ability, HumanDTO human, GhostDTO ghost) {
        if (ability == null || human == null || ghost == null) {
            throw new IllegalArgumentException("one of parametrs is null");
        }
        if (ability.getId() == null) {
            throw new IllegalArgumentException("Ability is not yet created.");
        }
        if (human.getId() == null) {
            throw new IllegalArgumentException("Human is not yet created.");
        }
        if (ghost.getId() == null) {
            throw new IllegalArgumentException("Ghost is not yet created.");
        }

        if (human.getPsyche() - ability.getDamage() < 1) {
            this.removeHumanFromHouse(human.getHome(), human);
             human.setPsyche(human.getPsyche() - ability.getDamage());
        }else{
            human.setPsyche(human.getPsyche() - ability.getDamage());
            this.updateHuman(human);
        }
        
        if(ability.getDamage() < 5){
            this.gainExperience(ghost, 1);
        }else{
            this.gainExperience(ghost, (int)(ability.getDamage() / 5));
        }
    }

    /**
     * Adds experience to ghost, call checkLvl method to check if ghost has changed his lvl. This method will be called in each successful use of
     * useAbility method
     *
     * @param ghost Player's ghost.
     * @param experience Gained experiences.
     */
    @Transactional
    private void gainExperience(GhostDTO ghost, Integer experience) {
        if (ghost == null || experience == null) {
            throw new IllegalArgumentException("ghost or exp is null");
        }
        if (ghost.getId() == null) {
            throw new IllegalArgumentException("Ghost is not yet created.");
        }
        if (experience < 1) {
            throw new IllegalArgumentException("Experience is not valid.");
        }

        ghost.setExperience(ghost.getExperience() + experience);
        checkLvl(ghost);
        ghostService.update(ghost);
        
    }

    /**
     * Levels up a ghost, giving user a selection of abilities his ghost can gain. Based on ghosts type.
     *
     * @param ghost Player's ghost.
     */
    @Transactional
    private void levelUp(GhostDTO ghost) {
        if (ghost == null) {
            throw new IllegalArgumentException("ghost is null");
        }
        if (ghost.getId() == null) {
            throw new IllegalArgumentException("Ghost is not yet created.");
        }

        ghost.setLvl(ghost.getLvl() + 1);
        ghostService.update(ghost);
        checkLvl(ghost);
    }
    
    /**
     * Checks if ghost has enough experience to lvl up, if so, levelUp method will be called This method also contains the experience amount needed
     * for each level.
     *
     * @param ghost Player's ghost.
     */
    @Transactional
    private void checkLvl(GhostDTO ghost) {
        if (ghost == null) {
            throw new IllegalArgumentException("ghost is null");
        }
        if (ghost.getId() == null) {
            throw new IllegalArgumentException("Ghost is not yet created.");
        }

        Integer nextLevelExperiences = -1;
        for (Level level : Level.values()) {
            
            if (level.toString().contains(String.valueOf(ghost.getLvl() + 1))) {
                nextLevelExperiences = level.experiences();
                break;
            }
        }
        
        if (nextLevelExperiences != -1 && ghost.getExperience() >= nextLevelExperiences) {
            levelUp(ghost);
        }
    }

    /**
     * Will be called when ghost reached some significant level. User will choose the ability to add
     *
     * @param ghost Player's ghost.
     * @param ability Ability to be added.
     */
    @Transactional
    @Override
    public void grantNewAbility(GhostDTO ghost, AbilityDTO ability) {
        if (ghost == null || ability == null) {
            throw new IllegalArgumentException("ghost or ability is null");
        }
        if (ghost.getId() == null) {
            throw new IllegalArgumentException("Ghost is not yet created.");
        }
        if (ability.getId() == null) {
            throw new IllegalArgumentException("Ability is not yet created.");
        }

        List<AbilityDTO> abilities = ghost.getAbilities();
        if (!(abilities.contains(ability))) {
            abilities.add(ability);
            ghost.setAbilities(abilities);
            ghostService.update(ghost);
        } else {
            throw new GhostMasterException("Selected ability cannot be added.");
        }
    }
    
    /**
     * Removes ability from ghost.
     * 
     * @param ghost Player's ghost.
     * @param ability Ability to be removed.
     */
    @Override
    public void removeAbilityFromGhost(GhostDTO ghost, AbilityDTO ability) {
        if (ghost == null || ability == null) {
            throw new IllegalArgumentException("ghost or ability is null");
        }
        if (ghost.getId() == null) {
            throw new IllegalArgumentException("Ghost is not yet created.");
        }
        if (ability.getId() == null) {
            throw new IllegalArgumentException("Ability is not yet created.");
        }
        
        List<AbilityDTO> abilities = ghost.getAbilities();
        if (abilities.remove(ability)) {
            ghost.setAbilities(abilities);
            ghostService.update(ghost);
        } else {
            throw new GhostMasterException("Selected ability cannot be removed.");
        }
    }

    @Override
    public void createAbility(AbilityDTO ability) {
        abilityService.create(ability);
    }

    @Override
    public void updateAbility(AbilityDTO ability) {
        abilityService.update(ability);
    }

    @Override
    public void removeAbility(AbilityDTO ability) {
        abilityService.remove(ability);
    }

    @Override
    public AbilityDTO getAbility(Long id) {
        return (abilityService.get(id));
    }

    @Override
    public List<AbilityDTO> findAllAbilities() {
        return abilityService.findAll();
    }

    @Override
    public List<AbilityDTO> findAbilitiesByName(String name) {
        return abilityService.findByName(name);
    }

    @Override
    public List<AbilityDTO> findAbilitiesByType(AbilityType at) {
        return abilityService.findByType(at);
    }

    @Override
    public void createHuman(HumanDTO human) {
        humanService.create(human);
    }

    @Override
    public void updateHuman(HumanDTO human) {
        humanService.update(human);
    }

    @Override
    public void removeHuman(HumanDTO human) {
        humanService.remove(human);
    }

    @Override
    public HumanDTO getHuman(Long id) {
        return humanService.get(id);
    }

    @Override
    public List<HumanDTO> findHumansByName(String name) {
        return humanService.findByName(name);
    }

    @Override
    public List<HumanDTO> findAllHumans() {
        return humanService.findAll();
    }

    @Override
    public List<HumanDTO> findHumansByHouse(HouseDTO house) {
        return humanService.findByHouse(house);
    }

    @Override
    public AccountDTO loginUser(String username, String password) {
        return securityService.login(username, password);
    }

    @Override
    public AccountDTO registerUser(AccountDTO acc) {
        return securityService.register(acc);
    }
}
